package com.muyu.util;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @ClassName: Uploader
 * @Author: Zirun.Xin
 * @Date: 2025/4/25 上午10:39
 * @Version: 1.0
 * @Description: 文件/图片等上传组件
 */
@Component
public class Uploader {

    /**
     * 上传图片文件到指定路径
     *
     * @param image 上传的图片文件
     * @param path  目标存储路径
     * @throws IOException              如果发生I/O错误
     * @throws IllegalArgumentException 如果参数无效
     */
    public void uploadImage(MultipartFile image, String path) throws IOException {
        // 参数校验
        if (image == null || image.isEmpty()) {
            throw new IllegalArgumentException("上传文件不能为空");
        }
        if (path == null || path.trim().isEmpty()) {
            throw new IllegalArgumentException("存储路径不能为空");
        }

        // 创建目标目录（如果不存在）
        File targetFile = new File(path);
        File parentDir = targetFile.getParentFile();

        if (parentDir != null && !parentDir.exists()) {
            if (!parentDir.mkdirs()) {
                throw new IOException("无法创建目标目录: " + parentDir.getAbsolutePath());
            }
        }

        // 检查文件是否已存在
        if (targetFile.exists()) {
            throw new IOException("目标文件已存在: " + path);
        }

        // 安全写入文件（先写入临时文件，再重命名）
        File tempFile = new File(path + ".tmp");
        try {
            image.transferTo(tempFile);
            if (!tempFile.renameTo(targetFile)) {
                throw new IOException("无法将临时文件重命名为目标文件");
            }
        } catch (IOException e) {
            // 清理失败的临时文件
            if (tempFile.exists()) {
                Files.deleteIfExists(tempFile.toPath());
            }
            throw e;
        }
    }

    /**
     * 将指定文件添加到ZIP输出流中
     *
     * @param filePath 要压缩的文件的绝对路径
     * @param zipOut   ZIP输出流，用于写入压缩数据
     * @throws IOException              如果发生I/O错误，包括：
     *                                  - 文件不存在或不可读
     *                                  - 无法创建ZIP条目
     *                                  - 写入ZIP流失败
     * @throws IllegalArgumentException 如果filePath为null或空字符串
     * @apiNote 此方法会自动关闭文件输入流，但不会关闭传入的zipOut流，
     * 调用方需要负责关闭zipOut流
     */
    public void fileToZip(String filePath, ZipOutputStream zipOut) throws IOException {
        // 参数校验
        if (filePath == null || filePath.trim().isEmpty()) {
            throw new IllegalArgumentException("文件路径不能为空");
        }
        if (zipOut == null) {
            throw new IllegalArgumentException("ZIP输出流不能为null");
        }

        File file = new File(filePath);
        if (!file.exists()) {
            throw new FileNotFoundException(String.format("文件不存在: %s", filePath));
        }
        if (!file.isFile()) {
            throw new IOException(String.format("路径不是文件: %s", filePath));
        }
        if (!file.canRead()) {
            throw new IOException(String.format("文件不可读: %s", filePath));
        }

        // 使用try-with-resources确保流被正确关闭
        try (FileInputStream fileInputStream = new FileInputStream(file);
             BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream, 1024 * 10)) {

            // 创建zip条目
            ZipEntry zipEntry = new ZipEntry(file.getName());
            zipOut.putNextEntry(zipEntry);

            // 缓冲区大小可根据实际需求调整
            byte[] buffer = new byte[1024 * 10];
            int bytesRead;

            // 读取文件内容并写入ZIP流
            while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
                zipOut.write(buffer, 0, bytesRead);
            }

            // 关闭当前条目
            zipOut.closeEntry();
        } catch (IOException e) {
            // 添加更多上下文信息后重新抛出
            throw new IOException(String.format("压缩文件失败: %s", filePath), e);
        }
    }

    /**
     * 预览图片
     *
     * @param response   HttpServletResponse对象，用于输出文件流
     * @param targetPath 文件路径
     * @throws IOException 如果文件不存在、读取失败或输出流异常
     */
    public void previewImage(HttpServletResponse response, String targetPath) throws IOException {
        if (response == null) {
            throw new IllegalArgumentException("HTTP响应对象不能为空");
        }

        if (targetPath == null || targetPath.trim().isEmpty()) {
            throw new IllegalArgumentException("文件路径不能为空");
        }

        // 使用try-with-resources自动关闭资源
        try (FileInputStream fis = new FileInputStream(targetPath);
             OutputStream os = response.getOutputStream()) {

            // 设置缓冲区大小（1MB）
            byte[] buffer = new byte[1024 * 1024];
            int bytesRead;

            // 读取文件并写入输出流
            while ((bytesRead = fis.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.flush();

        } catch (FileNotFoundException e) {
            throw new FileNotFoundException(String.format("文件不存在或路径错误: %s", targetPath));
        } catch (SecurityException e) {
            throw new SecurityException(String.format("没有权限访问文件: %s", targetPath), e);
        } catch (IOException e) {
            throw new IOException("文件读取或写入过程中发生错误", e);
        }
    }

    /**
     * 下载图片文件到HTTP响应流
     *
     * @param targetPath 要下载的文件路径
     * @param response   HTTP响应对象
     * @throws IOException              如果发生I/O错误
     * @throws IllegalArgumentException 如果参数无效
     */
    public void downloadImage(String targetPath, HttpServletResponse response) throws IOException {
        // 参数校验
        Objects.requireNonNull(response, "HTTP响应对象不能为空");
        if (targetPath == null || targetPath.trim().isEmpty()) {
            throw new IllegalArgumentException("文件路径不能为空");
        }

        // 获取文件名
        String fileName = new File(targetPath).getName();

        // 设置响应头
        response.setHeader("Content-Disposition",
                "attachment;filename=\"" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()) + "\"");
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);

        // 使用try-with-resources确保资源自动关闭
        try (FileInputStream fis = new FileInputStream(targetPath);
             ServletOutputStream outputStream = response.getOutputStream()) {

            // 使用更高效的缓冲区大小
            byte[] buffer = new byte[8192];
            int bytesRead;

            while ((bytesRead = fis.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }

            outputStream.flush();
        }
    }
}
